﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace P2PTransmit.ViewModal.Service.Core
{
    public class TransmitCore : IDisposable
    {
        public event Action<string>                      OnFileError;
        public event Action<string>                      OnNetworkError;
        public event Action<string>                      OnTransmitError; 
        public event Action<Modal.NetworkTask>        OnTaskFinish;
        const int                                     FrameSize= 133 * 1024;
        Socket                                        socket;
        string                                        SavePath;
        string                                        ErrorPath;
        public int Port { get; set; }

        public TransmitCore(string save, string error, bool server)
        {
            socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
            socket.Bind( new IPEndPoint( ServiceManager.LocalIPAddress, 0 ) );
            if ( server )
            {
                socket.Listen( 2 );
            }
            Port = ( (IPEndPoint) socket.LocalEndPoint ).Port;
            SavePath = save;
            ErrorPath = error;
        }

        public void Connect(Modal.NetworkTask task)
        {
            task.State = Modal.NetworkState.Transmitting;
            Task ta = new Task( () =>
             {
                 var ip = new IPEndPoint( IPAddress.Parse( task.IPAddress ), task.Port );
                 var dog = new Stopwatch();
                 int error = 0;
                 try
                 {
                     socket.Connect( ip );
                     using ( var ns = new NetworkStream( socket ) )
                     {
                         var bw = new BinaryWriter( ns );
                         var br = new BinaryReader( ns );
                         if ( InitializeCheckIndex_Server( task, bw ) )
                         {
                             task.StartStamp = DateTime.Now.Ticks;
                             InitializeSetIndex( task.CurrentFileIndex, bw );
                             for ( ; task.CurrentFileIndex < task.FileCount; task.CurrentFileIndex++ )
                             {
                                 task.CurrentFileLength = task.Files[task.CurrentFileIndex].FileLength;
                                 task.CurrentFileName = task.Files[task.CurrentFileIndex].FilePath;
                                 try
                                 {

                                     using ( var fs = File.OpenRead( task.CurrentFileName ) )
                                     {
                                         task.State = Modal.NetworkState.Transmitting;
                                         var FrameCount = fs.Length / FrameSize;
                                         var EndSize = fs.Length % FrameSize;
                                         var Data = new byte[FrameSize];
                                         try
                                         {
                                             while ( FrameCount-- > 0 )
                                             {
                                                 fs.Read( Data, 0, Data.Length );
                                                 dog.Start();
                                                 TransmitFrame_Server( Data, Data.Length, br, bw, ns );
                                                 dog.Stop();
                                                 task.Ping = dog.ElapsedMilliseconds;
                                                 dog.Reset();
                                                 task.CurrentPosition += FrameSize;

                                             }
                                             if ( EndSize > 0 )
                                             {
                                                 fs.Read( Data, 0, (int) EndSize );
                                                 dog.Start();
                                                 TransmitFrame_Server( Data, (int) EndSize, br, bw, ns );
                                                 dog.Stop();
                                                 task.Ping = dog.ElapsedMilliseconds;
                                                 dog.Reset();
                                                 task.CurrentPosition += EndSize;
                                             }
                                         }
                                         catch ( SocketException se )
                                         {
                                             //传输错误
                                             error++;
                                             task.State = Modal.NetworkState.Error;
                                             OnTransmitError?.Invoke( se.Message );
                                             SaveErrorToken( task );
                                         }
                                         if ( task.CurrentFileIndex != task.FileCount )
                                             task.CurrentPosition = 0;
                                     }
                                 }
                                 catch(IOException io)
                                 {
                                     //打开文件错误
                                     error++;
                                     task.State = Modal.NetworkState.Error;
                                     OnFileError?.Invoke(io.Message);
                                     SaveErrorToken( task );
                                 }
                             }
                         }
                     }
                 }
                 catch(SocketException ex)
                 {
                     //网络错误
                     error++;
                     task.State = Modal.NetworkState.Error;
                     OnNetworkError?.Invoke( ex.Message );
                     SaveErrorToken( task );
                 }
                 if ( task.State == Modal.NetworkState.Transmitting )
                 {
                     task.State = Modal.NetworkState.Finish;
                     OnTaskFinish?.Invoke( task );
                 }
             } );
            ta.Start();
        }

        public void Accept(ref Modal.NetworkTask t)
        {
            Task ta = new Task( (obj) =>
            {
                var dog = new Stopwatch();
                var task = (Modal.NetworkTask) obj;                
                int error = 0;
                try
                {
                    task.State = Modal.NetworkState.Transmitting;
                    var client = socket.Accept();
                    using ( var ns = new NetworkStream( client ) )
                    {
                        var bw = new BinaryWriter( ns );
                        var br = new BinaryReader( ns );
                        if ( InitializeCheckIndex_Client( task, br ) )
                        {
                            task.CurrentFileIndex = InitializeGetIndex( br );
                            task.StartStamp = DateTime.Now.Ticks;
                            for ( ; task.CurrentFileIndex < task.FileCount; task.CurrentFileIndex++ )
                            {
                                task.CurrentFileLength = task.Files[task.CurrentFileIndex].FileLength;
                                task.CurrentFileName = Path.Combine( SavePath, task.Files[task.CurrentFileIndex].FileName );
                                try
                                {

                                    using ( var fs = File.Create( task.CurrentFileName ) )
                                    {
                                        var FrameCount = task.CurrentFileLength / FrameSize;
                                        var EndSize = task.CurrentFileLength % FrameSize;
                                        var Data = new byte[FrameSize];
                                        try
                                        {
                                            while ( FrameCount-- > 0 )
                                            {
                                                dog.Start();
                                                TransmitFrame_Client( Data, FrameSize, br, bw, ns );
                                                dog.Stop();
                                                task.Ping = dog.ElapsedMilliseconds;
                                                dog.Reset();
                                                fs.Write( Data, 0, Data.Length );
                                                task.CurrentPosition += FrameSize;
                                            }
                                            if ( EndSize > 0 )
                                            {
                                                dog.Start();
                                                TransmitFrame_Client( Data, (int) EndSize, br, bw, ns );
                                                dog.Stop();
                                                task.Ping = dog.ElapsedMilliseconds;
                                                dog.Reset();
                                                fs.Write( Data, 0, (int) EndSize );
                                                task.CurrentPosition += EndSize;
                                            }
                                        }
                                        catch(SocketException se)
                                        {
                                            //传输错误
                                            error++;
                                            task.State = Modal.NetworkState.Error;
                                            OnTransmitError?.Invoke( se.Message );
                                            SaveErrorToken( task );
                                        }
                                        if ( task.CurrentFileIndex != task.FileCount )
                                            task.CurrentPosition = 0;
                                    }
                                }
                                catch(IOException io)
                                {
                                    //文件错误
                                    error++;
                                    task.State = Modal.NetworkState.Error;
                                    OnFileError?.Invoke(io.Message);
                                    SaveErrorToken( task );
                                }
                            }
                        }
                    }
                }
                catch ( SocketException ex )
                {
                    //网络错误
                    error++;
                    task.State = Modal.NetworkState.Error;
                    OnNetworkError?.Invoke( ex.Message );
                    SaveErrorToken( task );
                }
                if ( task.State == Modal.NetworkState.Transmitting )
                {
                    task.State = Modal.NetworkState.Finish;
                    OnTaskFinish?.Invoke( task );
                }
            }, t );
            ta.Start();
        }

        bool InitializeCheckIndex_Server(Modal.NetworkTask task, BinaryWriter bw)
        {
            if ( task.CurrentFileIndex > task.FileCount )
            {
                bw.Write( false );
                return false;
            }
            bw.Write( true );
            return true;
        }
        bool InitializeCheckIndex_Client(Modal.NetworkTask task, BinaryReader br)
        {
            return br.ReadBoolean();
        }
        void InitializeSetIndex(int index, BinaryWriter bw)
        {
            bw.Write( index );
        }
        int InitializeGetIndex(BinaryReader br)
        {
            return br.ReadInt32();
        }
        void TransmitFrame_Server(byte[] data, int length, BinaryReader br, BinaryWriter bw, NetworkStream ns)
        {
            bw.Write( length );
            if ( br.ReadInt32() == length )
            {
                bw.Write( data, 0, length );
            }
            else
            {
                //传输错误
                throw new Exception( "传输错误，数据块大小不匹配" );
            }
        }
        void TransmitFrame_Client(byte[] data, int length, BinaryReader br, BinaryWriter bw, NetworkStream ns)
        {
            if ( br.ReadInt32() == length )
            {
                bw.Write( length );
                br.Read( data, 0, length );
            }
            else
            {
                bw.Write( 0 );
                throw new Exception( "传输错误，数据丢失" );
            }
        }
        void SaveErrorToken(Modal.NetworkTask task)
        {
            //File.WriteAllText( Path.Combine( ErrorPath, task.Hash ), Newtonsoft.Json.JsonConvert.SerializeObject( task ) );
        }

        public void Dispose()
        {
            try
            {
                socket.Shutdown( SocketShutdown.Both );
            }
            catch
            {
                socket.Close();
            }
        }
    }
}
